/**
 * Copyright (c) 2005-2009 springside.org.cn
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * 
 * $Id: HibernateUtils.java 1066 2010-04-30 16:25:19Z calvinxiu $
 */
package net.uman.framework.modules.orm.hibernate;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import net.uman.entity.IdEntity;
import net.uman.framework.modules.objectpool.ObjectPoolFactory;
import net.uman.framework.modules.orm.PropertyFilter;
import net.uman.framework.modules.utils.ApplicationContextProvider;
import net.uman.framework.modules.utils.ReflectionUtils;
import net.uman.framework.modules.web.ServletUtils;
import net.uman.service.UserManager;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Hibernate;
import org.springframework.util.Assert;

/**
 * Hibernate针对Web应用的工具类.
 * 
 * @author calvin
 */
public class HibernateUtils {
//	private static ThreadLocal resolvedEntities = new ThreadLocal();   
	private static ThreadLocal<Boolean> ignoreResolve = new ThreadLocal<Boolean>();   

	private HibernateUtils() {
	}

	public static <T extends IdEntity> T resolveEntity(T attribute) {
		if (attribute == null || !ObjectPoolFactory.getInstance().isEnable()) {
			return attribute;
		}
		
//		if (Boolean.TRUE.equals(ignoreResolve.get())) {
//			return attribute;
//		}
//		
//		System.out.println("resolveEntity "+attribute.hashCode());
		try {
			T attr = attribute;
			if (attr != null && !Hibernate.isInitialized(attr)) {
				Long id = attr.getId();
				if (id != null) {
					String cls = attr.getClass().getSimpleName();
					if (cls.contains("_$$_")) {
						cls = attr.getClass().getSuperclass().getSimpleName();
					}
					String managerCls = cls +"Dao";
					managerCls = lowerCaseFirst(managerCls);
					HibernateDao dao = (HibernateDao)ApplicationContextProvider.getBean(managerCls);
					Method method = null;
					try {
						method = dao.getClass().getMethod("get", Serializable.class);
					} catch (NoSuchMethodException e) {
						method = dao.getClass().getMethod("get", Long.class);
					}
//					Boolean b = ignoreResolve.get();
//					ignoreResolve.set(Boolean.TRUE);
					Object ret = method.invoke(dao, id);
//					ignoreResolve.set(b);
					return (T)ret;
				}
			}
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return attribute;
	}
	
	private static String lowerCaseFirst(String s) {
		// if first two letters are all uppercase, should not set the first to be lowercase 
		if (Character.isUpperCase(s.charAt(0)) && Character.isUpperCase(s.charAt(1))) {
			return s;
		}
		return s.substring(0, 1).toLowerCase()+s.substring(1);
	}
	
	private static Class getEntityClass(IdEntity entity) {
		Class cls = entity.getClass();
		if (cls.getSimpleName().contains("_$$_")) {
			cls = cls.getSuperclass();
		}
		return cls;
	}
	
	public static <T extends IdEntity> List<T> resolveEntities(IdEntity owner, List<T> attrs, String attrName) {
		if (attrs == null || !ObjectPoolFactory.getInstance().isEnable()) {
			return attrs;
		}
		
//		System.out.println("resolveEntities "+attrName+" "+attrs.hashCode()+" "+ignoreResolve.get());
//		if (Boolean.TRUE.equals(ignoreResolve.get())) {
//			List<T> _attrs = new ArrayList<T>();
//			for (T attr : attrs) {
//				_attrs.add(attr);
//			}
//			return _attrs;
//		}
//		
//		List<String> ens = (List<String>)resolvedEntities.get();
//		if (ens == null) {
//			ens = new ArrayList<String>();
//			resolvedEntities.set(ens);
//		}
//		
//		String id = getEntityClass(owner).getSimpleName()+"@"+owner.getId();
//		if (ens.contains(id)) {
//			List<T> _attrs = new ArrayList<T>();
//			for (T attr : attrs) {
//				_attrs.add(attr);
//			}
//			return _attrs;
//		}
//		ens.add(id);
		
		try {
//			List<T> _attrs = new ArrayList<T>();
			if (!Hibernate.isInitialized(attrs)) {
//				// any DAO type should do
				try {
					Hibernate.initialize(attrs);
				} catch (Exception e) {
					UserManager manager = ApplicationContextProvider.getBean(UserManager.class);
					//attrs = manager.findPage("select a."+attrName+" from "+owner.getClass().getName()+" as a where a.id=?", Integer.MAX_VALUE, owner.getId());
				}
//				Boolean b = ignoreResolve.get();
//				ignoreResolve.set(Boolean.TRUE);
//				
//				_attrs = dao.findPage("select a."+attrName+" from "+owner.getClass().getName()+" as a where a.id=?", Integer.MAX_VALUE, owner.getId());
//				ignoreResolve.set(b);
				BeanUtils.setProperty(owner, attrName, attrs);
			} else {
				for (int i = 0 ; i < attrs.size() ; i++) {
					T attr = attrs.get(i);
					attr = resolveEntity(attr);
					attrs.set(i, attr);
				}
			}

			return attrs;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return attrs;
	}
	
	public static <T extends IdEntity> Set<T> resolveEntities(IdEntity owner, Set<T> attrs, String attrName) {
		if (attrs == null || !ObjectPoolFactory.getInstance().isEnable()) {
			return attrs;
		}
		
//		System.out.println("resolveEntities "+attrName+" "+attrs.hashCode()+" "+ignoreResolve.get());
//		if (Boolean.TRUE.equals(ignoreResolve.get())) {
//			Set<T> _attrs = new HashSet<T>();
//			for (T attr : attrs) {
//				_attrs.add(attr);
//			}
//			return _attrs;
//		}

		try {
			if (!Hibernate.isInitialized(attrs)) {
				// any DAO type should do
//				dao.attach(attrs);
				try {
					Hibernate.initialize(attrs);
				} catch (Exception e) {
					UserManager dao = ApplicationContextProvider.getBean(UserManager.class);
					//List<T> list = dao.findPage("select a."+attrName+" from "+owner.getClass().getName()+" as a where a.id=?", Integer.MAX_VALUE, owner.getId());
//					ignoreResolve.set(b);
//					Set<T> _attrs = new HashSet<T>();
//					for (T attr : list) {
//						_attrs.add(attr);
//					}
//					attrs = _attrs;
				}
//				Boolean b = ignoreResolve.get();
//				ignoreResolve.set(Boolean.TRUE);
//				List<T> list = dao.findPage("select a."+attrName+" from "+owner.getClass().getName()+" as a where a.id=?", Integer.MAX_VALUE, owner.getId());
//				ignoreResolve.set(b);
//				for (T attr : list) {
//					_attrs.add(attr);
//				}
				BeanUtils.setProperty(owner, attrName, attrs);
				return attrs;
			} else {
				for (T attr : attrs) {
					attr = resolveEntity(attr);
					attrs.add(attr);
				}
			}
			return attrs;
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		return attrs;
	}
	
	/**
	 * 根据对象ID集合,整理合并集合.
	 * 
	 * 默认对象主键的名称名为"id".
	 * 
	 * @see #mergeByCheckedIds(Collection, Collection, Class, String)
	 */
	public static <T, ID> void mergeByCheckedIds(final Collection<T> srcObjects, final Collection<ID> checkedIds,
			final Class<T> clazz) {
		mergeByCheckedIds(srcObjects, checkedIds, clazz, "id");
	}

	/**
	 * 根据对象ID集合,整理合并集合.
	 * 
	 * 页面发送变更后的子对象id列表时,删除原来的子对象集合再根据页面id列表创建一个全新的集合这种看似最简单的做法是不行的.
	 * 因此采用如此的整合算法：在源集合中删除id不在目标集合中的对象,根据目标集合中的id创建对象并添加到源集合中.
	 * 因为新建对象只有ID被赋值, 因此本函数不适合于cascade-save-or-update自动持久化子对象的设置.
	 * 
	 * @param srcObjects 源集合,元素为对象.
	 * @param checkedIds  目标集合,元素为ID.
	 * @param clazz  集合中对象的类型
	 * @param idName 对象主键的名称
	 */
	public static <T, ID> void mergeByCheckedIds(final Collection<T> srcObjects, final Collection<ID> checkedIds,
			final Class<T> clazz, final String idName) {

		//参数校验
		Assert.notNull(srcObjects, "scrObjects不能为空");
		Assert.hasText(idName, "idName不能为空");
		Assert.notNull(clazz, "clazz不能为空");

		//目标集合为空, 删除源集合中所有对象后直接返回.
		if (checkedIds == null) {
			srcObjects.clear();
			return;
		}

		//遍历源集合,如果其id不在目标ID集合中的对象,进行删除.
		//同时,在目标集合中删除已在源集合中的id,使得目标集合中剩下的id均为源集合中没有的id.
		Iterator<T> srcIterator = srcObjects.iterator();
		try {

			while (srcIterator.hasNext()) {
				T element = srcIterator.next();
				Object id;
				id = PropertyUtils.getProperty(element, idName);

				if (!checkedIds.contains(id)) {
					srcIterator.remove();
				} else {
					checkedIds.remove(id);
				}
			}

			//ID集合目前剩余的id均不在源集合中,创建对象,为id属性赋值并添加到源集合中.
			for (ID id : checkedIds) {
				T obj = clazz.newInstance();
				PropertyUtils.setProperty(obj, idName, id);
				srcObjects.add(obj);
			}
		} catch (Exception e) {
			throw ReflectionUtils.convertReflectionExceptionToUnchecked(e);
		}
	}

	public static PropertyFilter.RelationType getFilterRelationType(final HttpServletRequest request) {
		String rel = request.getParameter("filter_relation");
		return "OR".equalsIgnoreCase(rel) ? PropertyFilter.RelationType.OR : PropertyFilter.RelationType.AND; 
	}
	
	/**
	 * 根据按PropertyFilter命名规则的Request参数,创建PropertyFilter列表.
	 * 默认Filter属性名前缀为filter_.
	 * 
	 * @see #buildPropertyFilters(HttpServletRequest, String)
	 */
	public static List<PropertyFilter> buildPropertyFilters(final HttpServletRequest request) {
		return buildPropertyFilters(request, "filter_");
	}

	/**
	 * 根据按PropertyFilter命名规则的Request参数,创建PropertyFilter列表.
	 * PropertyFilter命名规则为Filter属性前缀_比较类型属性类型_属性名.
	 * 
	 * eg.
	 * filter_EQS_name
	 * filter_LIKES_name_OR_email
	 */
	@SuppressWarnings("unchecked")
	public static List<PropertyFilter> buildPropertyFilters(final HttpServletRequest request, final String filterPrefix) {
		List<PropertyFilter> filterList = new ArrayList<PropertyFilter>();

		//从request中获取含属性前缀名的参数,构造去除前缀名后的参数Map.
		Map<String, Object> filterParamMap = ServletUtils.getParametersStartingWith(request, filterPrefix);
		filterParamMap.remove("relation");
		
		//分析参数Map,构造PropertyFilter列表
		for (Map.Entry<String, Object> entry : filterParamMap.entrySet()) {
			String filterName = entry.getKey();
			Object value = entry.getValue();
			//如果value值为空,则忽略此filter.
			if (value instanceof String && StringUtils.isNotBlank((String)value)) {
				if (filterName.startsWith("IN") || filterName.startsWith("NIN")){	// IN or Not IN
					PropertyFilter filter = new PropertyFilter(filterName, ((String)value).split(","));
					filterList.add(filter);
				} else {				
					PropertyFilter filter = new PropertyFilter(filterName, (String)value);
					filterList.add(filter);
				}
			} else if (value instanceof String[] && ((String[])value).length > 0 && StringUtils.isNotBlank(((String[])value)[0])) {
				PropertyFilter filter = new PropertyFilter(filterName, (String[])value);
				filterList.add(filter);
			} else if (value == null || "null".equals(value) || "".equals(value) || "_ALL_".equals(value)){
				// ignore it
			} else if (value instanceof String && StringUtils.isBlank((String)value)) {
			   // ignore it
			} else {
				throw new RuntimeException("Unknown type for value:"+value);
			}
		}
		return filterList;
	}
}
